/****************************************************************************
* copyright            : (C) 2009 William Viana Soares <vianasw@gmail.com>  *
*****************************************************************************/

/****************************************************************************
*                                                                           *
*   This program is free software; you can redistribute it and/or modify    *
*   it under the terms of the GNU General Public License as published by    *
*   the Free Software Foundation; either version 2 of the License, or       *
*   (at your option) any later version.                                     *
*                                                                           *
*****************************************************************************/

#include "scene.h"
#include <boost/algorithm/string.hpp>

namespace ConciseQSR
{
    
Scene::Scene( vector< Point > &points, QueryMaker *qm )
    : m_ordPoints( points )
    , m_qm( qm )
    , m_queryTries( 0 )
{
    vector< Point >::iterator p;
    for( p = points.begin(); p != points.end(); p++ )
    {
        m_points[ p->label() ] = *p;
    }
    qualifyPoints();    
}

Scene::~Scene()
{}

void Scene::aClosure( Point &point, Reference &ref )
{
    if( !m_qm )
    {
        cerr << "Failed in Scene::qualifyPoints(): ";
        cerr << "Scene doesn't have a QueryMaker." << endl;
        throw 1;
    }
    
    //NOTE: remember to check if there is any problem if m_constraints is an empty string
    string constraints = "(" + m_constraints + ") ((" + ref.first().label() + " " + ref.second().label() + " * " + point.label() + "))";
    string resp;
    resp = m_qm->aClosure( constraints );
    boost::erase_tail(resp, 1);
    boost::erase_head(resp, 18);
    m_constraints = resp;
    
    const char *re1 = "\\(\\w[\\w\\d]* \\w[\\w\\d]* \\(\\d_\\d(?: \\d_\\d)*\\) \\w[\\w\\d]*\\)";
    const char *re2 =  "\\s\\(\\d_\\d(?: \\d_\\d)*\\)\\s";
    boost::regex constraintPattern( re1, boost::regex::perl );
    boost::regex baseRelPattern( re2, boost::regex::perl );
   
    std::string::const_iterator start, end, startC, endC;
    start = resp.begin();
    end = resp.end();   
    boost::match_results<std::string::const_iterator> constraint, baseRelations;
    boost::match_flag_type flags = boost::match_default;
    boost::match_flag_type flagsC = boost::match_default;
    vector< string > tokens;
    vector< string >::iterator br;
    string aux, la, lb, lc, lab;
    
    //search for constraints
    while( boost::regex_search( start, end, constraint, constraintPattern, flags ) )   
    {      
        aux = constraint[ 0 ].str();
        boost::erase_head( aux, 1 );
        boost::erase_tail( aux, 1 );
        startC = aux.begin();
        endC = aux.end();
        
        //search for the base relations on each constraint 
        while( boost::regex_search( startC, endC, baseRelations, baseRelPattern, flagsC ) )   
        {
            lab = baseRelations.prefix(); //reference
            boost::trim( lab );
            lc = baseRelations.suffix(); //point
            boost::trim( lc );
            
            boost::split( tokens, lab, boost::is_any_of( " " ) );
            la = tokens[ 0 ];
            lb = tokens[ 1 ];
            aux = baseRelations[ 0 ].str();
            Reference reference( m_points[ la ], m_points[ lb ] );
            boost::trim( aux );
            boost::trim_left_if( aux, boost::is_any_of( "(" ) );
            boost::trim_right_if( aux, boost::is_any_of( ")" ) );
            boost::split( tokens, aux, boost::is_any_of( " " ) );
            for( br = tokens.begin(); br != tokens.end(); br++ )
            {                                
                m_points[ lc ].addRelation( *br, reference );
            }
            
            // update search position:
            startC = baseRelations[ 0 ].second;      
            // update flags:
            flagsC |= boost::match_prev_avail;
            flagsC |= boost::match_not_bob;
        }
        // update search position:
        start = constraint[0].second;      
        // update flags:
        flags |= boost::match_prev_avail;
        flags |= boost::match_not_bob;
    }        
}

void Scene::addPoint( Point point, Point refPointA, Point refPointB )
{
    addPoint( point, Reference( refPointA, refPointB ) );
}

void Scene::addPoint( Point point, Reference ref )
{
    vector< string > tokens;
    string pointsStr, resp, baseRelation;
    Point c( point );
    Point a( ref.first() );
    Point b( ref.second() );
    
    if( m_points.find( point.label() ) == m_points.end() )
    {        
        pointsStr = "(" + a.str() + b.str() + c.str() + ")";
        resp = m_qm->qualifyPoints( pointsStr );                        
        boost::split( tokens, resp, boost::is_any_of( " " ) );
        baseRelation = tokens[ 2 ];

        m_ordPoints.push_back( point );
        m_points[ point.label() ] = point;
        
        m_points[ c.label() ].addRelation( baseRelation, ref );
        boost::erase_tail( resp, 2 );
        boost::erase_head( resp, 1 );
        m_constraints += resp;
                
    }
    else if( !m_points[ c.label() ].hasRelationWith( ref ) )
    {
        pointsStr = "(" + a.str() + b.str() + c.str() + ")";
        resp = m_qm->qualifyPoints( pointsStr );                        
        boost::split( tokens, resp, boost::is_any_of( " " ) );
        baseRelation = tokens[ 2 ];
        
        m_points[ c.label() ].addRelation( baseRelation, ref );
        boost::erase_tail( resp, 2 );
        boost::erase_head( resp, 1 );
        m_constraints += resp;
    }

    if( m_points.find( a.label() ) == m_points.end() )
    {
        m_points[ a.label() ] = a;
        m_ordPoints.push_back( a );
    }
    if( m_points.find( b.label() ) == m_points.end() )
    {
        m_points[ b.label() ] = b;
        m_ordPoints.push_back( b );
    }
}

vector< string > Scene::getRelations( Point &p, Reference &ref )
{
    vector< string > baseRelations;
    string a, b, c;
    string transfRel;
    Reference ca, ab, cb;
    c = p.label();
    a = ref.first().label();
    b = ref.second().label();
    ca = Reference( p, ref.first() );
    ab = ref;
    cb = Reference( p, ref.second() );
    
    if( m_points.find( c ) != m_points.end() )
    {
        map< Reference, set< BaseRelation > > rel = m_points[ c ].getRelations();
        map< Reference, set< BaseRelation > >::iterator it = rel.find( ab );
        set< BaseRelation >::iterator br;
        
        if( it != rel.end() ) //if true the point has a relation with reference system "ab"
        {            
            if( it->first.sameOrientation( ab ) )
            {                
                for( br = it->second.begin(); br != it->second.end(); br++ )
                    baseRelations.push_back( *br );
            }
            else
            {
                for( br = it->second.begin(); br != it->second.end(); br++ )
                    baseRelations.push_back( m_qm->computeInverse( *br ) );
            }                        
        }
        else if( m_points[ a ].hasRelationWith( cb ) ) //let's try with point a and reference system cb
        {
            rel = m_points[ a ].getRelations();
            it = rel.find( cb );
            if( it->first.sameOrientation( cb ) )
            {                
                for( br = it->second.begin(); br != it->second.end(); br++ )
                {
                    transfRel = m_qm->computeHommingInverse( *br );
                    baseRelations.push_back( transfRel );
                    m_points[ c ].addRelation( transfRel, ref );
                }
            }
            else
            {
                for( br = it->second.begin(); br != it->second.end(); br++ )
                {
                    transfRel = m_qm->computeHomming( *br );
                    baseRelations.push_back( transfRel );
                    m_points[ c ].addRelation( transfRel, ref );
                }
            }
        }
        else if( m_points[ b ].hasRelationWith( ca ) ) //last try, point b ref sys ca
        {            
            rel = m_points[ b ].getRelations();
            it = rel.find( ca );
            if( it->first.sameOrientation( cb ) )
            {                
                for( br = it->second.begin(); br != it->second.end(); br++ )
                {
                    transfRel = m_qm->computeShortcutInverse( *br );
                    baseRelations.push_back( transfRel );
                    m_points[ c ].addRelation( transfRel, ref );
                }
            }
            else
            {
                for( br = it->second.begin(); br != it->second.end(); br++ )
                {
                    transfRel = m_qm->computeShortcut( *br );
                    baseRelations.push_back( transfRel );
                    m_points[ c ].addRelation( transfRel, ref );
                }
            }
        }
        else if( m_queryTries < MAX_TRIES )//give up, let's ask SparQ
        {
            m_queryTries++;
            aClosure( p, ref );
            baseRelations = getRelations( p, ref );
            m_queryTries = 0;
        }
    }
    return baseRelations;
}

void Scene::qualifyPoints()
{
    if( !m_qm )
    {
        cerr << "Failed in Scene::qualifyPoints(): ";
        cerr << "Scene doesn't have a QueryMaker." << endl;
        throw 1;
    }
    
    int vSize = m_ordPoints.size();
    string rel;
    string pointsStr;
    if( vSize > 2 )
    {
        vector< string > tokens;
        string baseRelation;
        Reference reference;
        Point a, b, c;
        for( int i = 0; i < vSize - 2 ; i++ )
        {               
            a = m_ordPoints[ i ];
            b = m_ordPoints[ i+1 ];
            c = m_ordPoints[ i+2 ];

            pointsStr = "(" + a.str() + b.str() + c.str() + ")";            
            rel = m_qm->qualifyPoints( pointsStr );
            reference = Reference( a, b );
                        
            boost::split( tokens, rel, boost::is_any_of( " " ) );
            baseRelation = tokens[ 2 ];

            m_points[ c.label() ].addRelation( baseRelation, reference );
            boost::erase_tail( rel, 2 );
            boost::erase_head( rel, 1 );
            m_constraints += rel;
        }
        a = m_ordPoints[ 0 ] ;
        b = m_ordPoints[ 1 ] ;
        c = m_ordPoints[ vSize-1 ] ;
        
        pointsStr = "(" + a.str() + b.str() + c.str() + ")";            
        rel = m_qm->qualifyPoints( pointsStr );
        
        reference = Reference( a, b );                    
        boost::split( tokens, rel, boost::is_any_of( " " ) );
        baseRelation = tokens[ 2 ];
        m_points[ c.label() ].addRelation( baseRelation, reference );
        boost::erase_tail( rel, 2 );
        boost::erase_head( rel, 1 );
        m_constraints += rel;
    }
    
}

void Scene::setQueryMaker( QueryMaker *qm )
{
    if( !m_qm )
    {
        m_qm = qm;
        qualifyPoints();
    }
    else
        m_qm = qm;
}

const QueryMaker* Scene::queryMaker() const
{
    return m_qm;
}

ostream& operator<<( ostream &os, const Scene &s )
{
    map< string, Point >::const_iterator point;
    
    for( point = s.m_points.begin(); point != s.m_points.end(); point++ )
    {
        point->second.showRelations();        
    }
    return os;
}

}//namespace ConciseQSR